Перейти к основному содержимому

5.02. Функции

Разработчику Архитектору

Функции

Функция — это именованный блок кода, предназначенный для выполнения определённой задачи. Она может принимать входные данные (аргументы), обрабатывать их и возвращать результат. Функции позволяют организовать код по принципу модульности: повторное использование, изоляция логики, упрощение отладки и тестирования.

В отличие от некоторых других языков программирования, таких как Java или C#, где методы строго связаны с классами и требуют явного указания типов возвращаемого значения и параметров, в Python функции являются автономными конструкциями, не привязанными к классам по умолчанию, а система типов — динамической. Это не означает, что типы отсутствуют, но они проверяются во время выполнения, а не на этапе компиляции.

Функции в Python обладают рядом характерных черт, отличающих их от аналогичных конструкций в строго типизированных языках:

  1. Динамическая типизация параметров и возвращаемых значений. Типы аргументов и возвращаемых значений не указываются в сигнатуре функции. Проверка корректности типов происходит только во время выполнения.
  2. Гибкая передача аргументов. Поддерживается комбинированное использование позиционных, именованных аргументов, а также распаковка коллекций через *args и словарей через **kwargs.
  3. Функции — объекты первого класса. Функции можно присваивать переменным, передавать как аргументы, возвращать из других функций. Эта особенность фундаментальна для понимания поведения функций в Python, даже если она будет рассмотрена подробнее позже.
  4. Отсутствие необходимости объявления типа возврата. Ключевое слово return используется для возврата значения, но тип возвращаемого результата не декларируется. Функция может возвращать разные типы в разных ветвях исполнения.
  5. Автоматическое создание локального пространства имён. При вызове функции создаётся новая область видимости, в которой существуют локальные переменные. Управление доступом к глобальным и нелокальным переменным осуществляется через явные инструкции.

Функция в Python объявляется с помощью ключевого слова def, за которым следует имя функции, список параметров в круглых скобках и двоеточие.

Тело функции представляет собой блок кода, выделенный отступом (обычно 4 пробела).

def greet(name):
return f"Hello, {name}!"

Синтаксис:

def <имя_функции>(<список_параметров>):
<тело функции>

Имя функции должно следовать правилам идентификаторов: начинаться с буквы или подчёркивания, содержать только буквы, цифры и подчёркивания. Рекомендуется использовать змеиный_регистр (snake_case) согласно PEP 8.

Тело функции форматируется с соблюдением отступов. Отступ определяет принадлежность строк к блоку. Все строки с одинаковым отступом считаются частью одного блока. Завершение отступа означает выход из области действия функции.

Параметр — переменная, указанная в сигнатуре функции.

Аргумент — фактическое значение, переданное функции при вызове.

Например, в вызове greet("Alice"), "Alice" — аргумент, а name в def greet(name): — параметр.

Позиционные параметры. Аргументы передаются в порядке, соответствующем порядку параметров в определении функции.

def add(a, b):
return a + b

result = add(3, 5) # a=3, b=5

Именованные параметры (ключевые аргументы). Аргументы передаются с указанием имени параметра. Это позволяет задавать аргументы в произвольном порядке.

result = add(b=5, a=3)

Именованные аргументы могут использоваться только после позиционных.

Параметры со значениями по умолчанию. Параметру может быть присвоено значение по умолчанию. Такой параметр становится необязательным при вызове.

def greet(name, greeting="Hello"):
return f"{greeting}, {name}!"

print(greet("Bob")) # Hello, Bob!
print(greet("Bob", "Hi")) # Hi, Bob!

Важно: параметры со значениями по умолчанию должны следовать после всех обязательных параметров. Значения по умолчанию вычисляются один раз при определении функции. Не рекомендуется использовать изменяемые объекты (например, списки или словари) в качестве значений по умолчанию.

Параметры до / — только позиционные.

Параметры после * — только именованные.

def func(pos_only, /, standard, *, kwd_only):
pass

Здесь:

  • pos_only можно передать только позиционно.
  • standard — и позиционно, и по имени.
  • kwd_only — только по имени.

Python предоставляет механизм для приёма произвольного числа аргументов. • *args — собирает все позиционные аргументы в кортеж. • **kwargs — собирает все именованные аргументы в словарь.

def log_call(*args, **kwargs):
print(f"Позиционные аргументы: {args}")
print(f"Именованные аргументы: {kwargs}")

log_call(1, 2, action="save", user="admin")
# Вывод:
# Позиционные аргументы: (1, 2)
# Именованные аргументы: {'action': 'save', 'user': 'admin'}

Эти механизмы часто используются при создании декораторов, базовых классов, функций-обёрток.

Распаковка аргументов при вызове:

args = [1, 2]
kwargs = {"c": 3, "d": 4}

def func(a, b, c, d):
return a + b + c + d

func(*args, **kwargs) # эквивалентно func(1, 2, c=3, d=4)

Оператор return завершает выполнение функции и возвращает указанное значение вызывающему коду. Если return отсутствует или не содержит значения, функция возвращает None.

def square(x):
return x ** 2

def do_nothing():
pass # вернёт None

result = do_nothing() # result == None

Функция может возвращать несколько значений — на практике это кортеж:

def divide_remainder(a, b):
return a // b, a % b

quotient, remainder = divide_remainder(10, 3)

Это синтаксический сахар для создания и распаковки кортежа.

В Python действует правило LEGB:

  • L — Local (локальная область — внутри функции),
  • E — Enclosing (объемлющая — вложенная функция),
  • G — Global (глобальная — модуль),
  • B — Built-in (встроенная — вроде print, len).

Переменные, объявленные внутри функции, по умолчанию являются локальными.

x = "global"

def outer():
x = "enclosing"

def inner():
x = "local"
print(x) # local

inner()
print(x) # enclosing

outer()
print(x) # global

Для изменения глобальной переменной внутри функции используется ключевое слово global:

counter = 0

def increment():
global counter
counter += 1

Для изменения переменной из объемлющей области — nonlocal:

def outer():
x = 1
def inner():
nonlocal x
x += 1
inner()
print(x) # 2

Без nonlocal интерпретатор создаст новую локальную переменную x, не затрагивая внешнюю.

Кроме именованных функций, объявляемых через def, Python поддерживает анонимные функции с помощью выражения lambda.

Синтаксис:

lambda <параметры>: <выражение>

Тело lambda — одно выражение (не блок кода). Не может содержать операторы (return, if, for и т.п.), кроме тернарного оператора. Не может иметь аннотаций типов (хотя это технически возможно, но не поддерживается стандартом).

Примеры:

square = lambda x: x ** 2
add = lambda a, b: a + b
is_even = lambda n: n % 2 == 0

lambda часто используется как аргумент для функций высшего порядка:

data = [(1, 'b'), (2, 'a'), (3, 'c')]
sorted(data, key=lambda x: x[1]) # сортировка по второму элементу

Хотя lambda удобна для кратких случаев, её использование не обязательно. Любая lambda-функция может быть заменена на обычную, определённую через def. Выбор зависит от читаемости и контекста.

Функция называется рекурсивной, если она вызывает саму себя.

Рекурсия — естественный способ решения задач, имеющих рекурсивную структуру (например, обход деревьев, вычисление факториала, последовательности Фибоначчи). Пример:

def factorial(n):
if n <= 1:
return 1
return n * factorial(n - 1)

Базовый случай — условие завершения рекурсии. Без него возникает бесконечная рекурсия и переполнение стека вызовов.

Глубина рекурсии в Python ограничена (по умолчанию ~1000). Её можно изменить с помощью sys.setrecursionlimit(), но это не рекомендуется из-за риска аварийного завершения.

Рекурсия может быть менее эффективной, чем итерация, из-за накладных расходов на вызовы. Однако она часто упрощает реализацию алгоритмов на основе разделяй-и-властвуй.

Иногда требуется объявить функцию, тело которой пока не реализовано. Для этого используется оператор-заглушка pass.

def todo():
pass # заглушка, ничего не делает

Также можно использовать ... (Ellipsis), хотя это менее распространено:

def stub():
...

Благодаря динамической типизации, гибкой системе аргументов и возможности возвращать функции, они служат основой для многих парадигм программирования — от процедурной до функциональной.

Функции первого класса

Функции первого класса
Функции как объекты: присваивание, передача, хранение.
Замыкания (closures): вложенные функции, захват переменных.
Декораторы: Что такое и зачем нужны.
Синтаксис @decorator.
Примеры: логирование, кэширование, проверка прав.
Декораторы с параметрами.
Применение: улучшение читаемости и повторного использования.

Термин «объект первого класса» (first-class object) происходит из теории языков программирования и означает сущность, которая:

  • Может быть присвоена переменной.
  • Может быть передана как аргумент другой функции.
  • Может быть возвращена из функции.
  • Может быть сохранена в структурах данных (списках, словарях, множествах и т.п.).

Если такой объект — функция, то говорят: функция является объектом первого класса. В Python все функции удовлетворяют этим критериям. Это не просто синтаксическая конструкция — это полноценные объекты, существующие в runtime, обладающие типом, атрибутами и поведением, как и любые другие данные. Важно: термин «первого класса» не означает «лучше» или «высокопроизводительнее». Он указывает на степень интеграции функций в систему типов и выполнения программы.

Например, в C функции нельзя напрямую хранить в списках или возвращать из других функций без использования указателей (что выходит за рамки базовой модели языка). В Java до версии 8 методы не были объектами первого класса — их можно было использовать только через интерфейсы или рефлексию.

В Python же функция — полноценный объект, создаваемый во время исполнения.

Поскольку функции в Python — объекты, они могут использоваться так же, как числа, строки или списки.

Присваивание функции переменной:

def greet(name):
return f"Hello, {name}!"

say_hello = greet # Присваиваем функцию переменной
print(say_hello("Alice")) # Вызов через новое имя

Здесь greet и say_hello — два имени, ссылающиеся на один и тот же объект-функцию.

Передача функции как аргумента:

def apply_operation(func, x):
return func(x)

def square(n):
return n ** 2

result = apply_operation(square, 5) # 25

Такой подход лежит в основе функций высшего порядка — функций, которые принимают или возвращают другие функции.

Хранение функций в структурах данных:

operations = {
'square': lambda x: x ** 2,
'double': lambda x: x * 2,
'negate': lambda x: -x
}

result = operations['square'](4) # 16

Это позволяет динамически выбирать поведение программы.

Проверка типа и атрибуты функции.

Функция — это объект типа function. Его можно исследовать:

print(type(greet))                    # <class 'function'>
print(callable(greet)) # True
print(greet.__name__) # 'greet'
print(greet.__module__) # имя модуля

Атрибуты функций можно расширять:

greet.author = "John Doe"
print(greet.author) # John Doe

Замыкание — это функция, которая «захватывает» переменные из своей объемлющей области видимости и продолжает иметь к ним доступ даже после завершения этой области.

Условия возникновения замыкания - наличие вложенной функции; вложенная функция ссылается на переменную из внешней функции; внешняя функция возвращает внутреннюю.

def make_multiplier(factor):
def multiply(x):
return x * factor # захватывает factor
return multiply

double = make_multiplier(2)
triple = make_multiplier(3)

print(double(5)) # 10
print(triple(5)) # 15

Здесь multiply — замыкание. Оно «помнит» значение factor, даже когда make_multiplier уже завершилась.

Когда функция возвращается, вместе с ней возвращается и ссылка на её окружение (cell objects), в котором хранятся захваченные переменные. Это обеспечивает инкапсуляцию состояния без использования классов. Проверить наличие замыкания можно через атрибут __closure__:

print(double.__closure__)           # (<cell at 0x...: int object at 0x...>,)
print(double.__closure__[0].cell_contents) # 2

Пример - счётчик:

def make_counter():
count = 0
def increment():
nonlocal count
count += 1
return count
return increment

counter = make_counter()
print(counter()) # 1
print(counter()) # 2

Декоратор — это функция, которая принимает другую функцию и возвращает новую функцию, расширяя или изменяя её поведение без модификации исходного кода. Декораторы реализуют принцип открытости/закрытости: объект (функция) открыт для расширения, но закрыт для изменения. Они позволяют вынести побочные эффекты (логирование, проверку прав, кэширование) в отдельные модули, улучшая читаемость и повторное использование.

Синтаксис @decorator. Без декоратора:

def my_function():
print("Основная логика")

def logger(func):
def wrapper():
print(f"Вызов: {func.__name__}")
func()
print("Вызов завершён")
return wrapper

my_function = logger(my_function)

С синтаксисом @:

@logger
def my_function():
print("Основная логика")

Оба варианта эквивалентны. Декоратор применяется при определении функции.

Декоратор с параметром — это функция, возвращающая декоратор. То есть трёхуровневая структура:

def repeat(times):
def decorator(func):
def wrapper(*args, **kwargs):
for _ in range(times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator

@repeat(times=3)
def say_hello():
print("Привет!")

Здесь repeat(3) возвращает декоратор. Декоратор применяется к say_hello.